# -*- coding: utf-8 -*-

'''
Created on 29/01/2012

@author: Luis M. Ugalde
www.forondarena.net

Copyright © 2011 Luis M. Ugalde
Licensed under the terms of the MIT License
(see LICENSE file for details)

This software can't be considered to be even in Alpha state. It still needs a lot of work.
'''


import yaml
import sys

class ConfigException(Exception): pass


class Config(object):
    '''
    Configuration management class
    '''


    def __init__(self, confile):
        '''
        Constructor
        '''
        try:
            f = open(confile)
            self._config = yaml.load(f)
            f.close()
        except IOError:
            print "Cant open config file: " + confile
            sys.exit(1)
        except yaml.YAMLError:
            print "Error parsing configuration file " + confile
            sys.exit(1)
            
        self._startreader = False
        self._startparser = False
        self._startstatestore = False
        self._startwriter = False
        
        try:
            if self._config['readers']['start'] == 1:
                self._readerconfig = self._config['readers'] 
                self._startreader = self._isValidConfig(self._readerconfig)
        except (KeyError, TypeError):
            print "Reader basic configuration is invalid. Skipping."
        try:
            if self._config['parsers']['start'] == 1:
                self._parserconfig = self._config['parsers']
                self._startparser = self._isValidConfig(self._parserconfig)
        except (KeyError, TypeError):
            print "Parser basic configuration is invalid. Skipping."
        try:
            if self._config['statestores']['start'] == 1:
                self._statestoreconfig = self._config['statestores']
                self._startstatestore = self._isValidConfig(self._statestoreconfig)
        except (KeyError, TypeError):
            print "State Store basic configuration is invalid. Skipping."
        try:
            if self._config['writers']['start'] == 1:
                self._writerconfig = self._config['writers']
                self._startwriter = self._isValidConfig(self._writerconfig)
        except (KeyError, TypeError):
            print "Writer basic configuration is invalid. Skipping."

    
    @staticmethod
    def _isValidConfig(conf):
        try:
            tmpvalidattr = reduce( (lambda x, y: x and y in conf.keys()), ['availableclasses', 'instances', 'usedclass'], True)
            if not tmpvalidattr:
                if __debug__: print "Required basic structure is missing in config file"
                return False
            if conf['usedclass'] == None:
                if __debug__: print "Config file should mention the class to use for the instances"
                return False
            tmpvalidattr = reduce( (lambda x, y: x and len(conf[y]) >= 1), ['availableclasses', 'instances'], True)
            if not tmpvalidattr:
                if __debug__: print "We should have at least one class type and one instance."
                return False
        except (AttributeError, TypeError, KeyError):
            if __debug__: print "An exception raised in _isValidConfig."
            return False
        
        return True


    def getReaderConfig(self, key = None):
        if self._startreader:
            if key == None:
                return self._readerconfig
            else:
                try:
                    return self._readerconfig[key]
                except KeyError:
                    raise ConfigException
        else:
            raise ConfigException
        

    def getParserConfig(self, key = None):
        if self._startparser:
            if key == None:
                return self._parserconfig
            else:
                try:
                    return self._parserconfig[key]
                except KeyError:
                    raise ConfigException
        else:
            raise ConfigException


    def getStateStoreConfig(self, key = None):
        if self._startstatestore:
            if key == None:
                return self._statestoreconfig
            else:
                try:
                    return self._statestoreconfig[key]
                except KeyError:
                    raise ConfigException
        else:
            raise ConfigException


    def getWriterConfig(self, key = None):
        if self._startwriter:
            if key == None:
                return self._writerconfig
            else:
                try:
                    return self._writerconfig[key]
                except KeyError:
                    raise ConfigException
        else:
            raise ConfigException


    def getReaderInstanceNumber(self):
        if self._startreader:
            return len(self.getReaderConfig('instances'))
        else:
            return 0

                       
    def getParserInstanceNumber(self):
        if self._startparser:
            return len(self.getParserConfig('instances'))
        else:
            return 0


    def getStateStoreInstanceNumber(self):
        if self._startstatestore:
            return len(self.getStateStoreConfig('instances'))
        else:
            return 0


    def getWriterInstanceNumber(self):
        if self._startwriter:
            return len(self.getWriterConfig('instances'))
        else:
            return 0

        
    def getReaderConfigInstance(self,insnum):
        if self._startreader:
            try:
                return ConfigInstance(self.getReaderConfig('instances')[insnum], 'reader')
            except KeyError:
                if __debug__: print "Error creating a reader configuration instance"
                raise ConfigException


    def getParserConfigInstance(self,insnum):
        if self._startparser:
            try:
                return ConfigInstance(self.getParserConfig('instances')[insnum], 'parser')
            except KeyError:
                if __debug__: print "Error creating a parser configuration instance"
                raise ConfigException


    def getStateStoreConfigInstance(self,insnum):
        if self._startstatestore:
            try:
                return ConfigInstance(self.getStateStoreConfig('instances')[insnum], 'statestore')
            except KeyError:
                if __debug__: print "Error creating a statestore configuration instance"
                raise ConfigException


    def getWriterConfigInstance(self,insnum):
        if self._startwriter:
            try:
                return ConfigInstance(self.getWriterConfig('instances')[insnum], 'writer')
            except KeyError:
                if __debug__: print "Error creating a parser configuration instance"
                raise ConfigException


    def startReader(self):
        return self._startreader

    
    def startParser(self):
        return self._startparser

    
    def startStateStore(self):
        return self._startstatestore

    
    def startWriter(self):
        return self._startwriter
        

class ConfigInstance(object):
    '''
    Instance configuration.
    Readers, Parsers, ... have 1..n instances
    '''
    
    
    def __init__(self, instancedata, argtype):
        tmpvalidattr = reduce( (lambda x, y: x and y in instancedata.keys()), ['data', 'notify'], True)
        # Options is not a required attribute, though it might be, eventually.
        if not tmpvalidattr:
            if __debug__: print "You need both a data and a notify block in your configuration yaml file."
            raise ConfigException
        tmpvalidattr = reduce( (lambda x, y: x and y in instancedata['data'].keys()), ['source', 'bind'], True)
        if not tmpvalidattr:
            if __debug__: print "You need both source and bind in your data channel."
            raise ConfigException
        tmpvalidattr = reduce( (lambda x, y: x and y in instancedata['notify'].keys()), ['source', 'bind'], True)
        if not tmpvalidattr:
            if __debug__: print "You need to at least mention source and bind in the notify channel, even if it is not used (writer)"
            raise ConfigException
        if argtype == 'reader':
            for i in instancedata['data']['source']:
                if instancedata['data']['source'] == None:
                    if __debug__: print "You need a data source for your reader."
                    raise ConfigException
        else:
            try:
                for i in instancedata['data']['source']:
                    if 'proto' not in i.keys(): raise ConfigException
                    if 'host' not in i.keys(): raise ConfigException
                    if 'port' not in i.keys(): raise ConfigException
                for i in instancedata['notify']['source']:
                    if 'proto' not in i.keys(): raise ConfigException
                    if 'host' not in i.keys(): raise ConfigException
                    if 'port' not in i.keys(): raise ConfigException
            except AttributeError, ConfigException:
                if __debug__: print "You need to define proto, host and port for your reader data and notify sources."
                raise ConfigException
            
        if argtype == 'writer':
            if instancedata['data']['bind'] == None:
                if __debug__: print "You need to bind a destination in your writer config."
                raise ConfigException
        else:
            try:
                if 'proto' not in instancedata['data']['bind'].keys(): raise ConfigException
                if 'host' not in instancedata['data']['bind'].keys(): raise ConfigException
                if 'port' not in instancedata['data']['bind'].keys(): raise ConfigException
                if 'proto' not in instancedata['notify']['bind'].keys(): raise ConfigException
                if 'host' not in instancedata['notify']['bind'].keys(): raise ConfigException
                if 'port' not in instancedata['notify']['bind'].keys(): raise ConfigException
            except (AttributeError, ConfigException):
                if __debug__: print "You need to define proto, host and port for your data and notify bind destinations."
                raise ConfigException

        self._instance = instancedata
        
        
    def getDataSource(self):
        try:
            return self._instance['data']['source']
        except (KeyError, AttributeError):
            raise ConfigException


    def getDataSourceLen(self):
        try:
            return len(self.getDataSource())
        except (KeyError, TypeError):
            return 0


    def getDataBind(self):
        try:
            return self._instance['data']['bind']
        except (KeyError, AttributeError):
            raise ConfigException


    def getOptions(self):
        try:
            return self._instance['options']
        except (KeyError, AttributeError):
            return None
    
    
    def getNotifySource(self):
        try:
            return self._instance['notify']['source']
        except (KeyError, AttributeError):
            raise ConfigException


    def getNotifySourceLen(self):
        try:
            return len(self.getNotifySource())
        except (KeyError, TypeError):
            return 0
    
    #
    # More error detection needed everywhere
    #


    def getNotifyBind(self):
        try:
            return self._instance['notify']['bind']
        except (KeyError, AttributeError):
            raise ConfigException


    def getDataSourceConfig(self, index = None, key = None):
        if not index == None:
            if key == None:
                return self.getDataSource()[index]
            else:
                try:
                    return self.getDataSource()[index][key]
                except KeyError:
                    raise ConfigException
        else:
            raise ConfigException


    def getDataBindConfig(self, key = None):
        if key == None:
            return self.getDataBind()
        else:
            try:
                return self.getDataBind()[key]
            except KeyError:
                raise ConfigException


    def getNotifySourceConfig(self, index = None, key = None):
        if not index == None:
            if key == None:
                return self.getNotifySource()[index]
            else:
                try:
                    return self.getNotifySource()[index][key]
                except KeyError:
                    raise ConfigException
        else:
            raise ConfigException
    
    
    def getNotifyBindConfig(self, key = None):
        if key == None:
            return self.getNotifyBind()
        else:
            try:
                return self.getNotifyBind()[key]
            except KeyError:
                raise ConfigException


    def getOptionsConfig(self, key = None):
        try:
            if key == None:
                return self.getOptions()
            else:
                return self.getOptions()[key]
        except KeyError:
            return None


if __name__ == '__main__':
    print "Please, do not run this"
        